﻿//    Copyright (C) 2013 Jay Herman

//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.

//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.

//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;
using ICSharpCode.SharpZipLib.GZip;
using ICSharpCode.SharpZipLib.Zip;

namespace APKExtractor
{
    class _APKFile
    {
        private string routeName = string.Empty;
        private string folderName = string.Empty;
        private List<_APKContents> fileContents = new List<_APKContents>();

        public string RouteName
        {
            get { return routeName; }
        }

        public string FolderName
        {
            get { return folderName; }
        }

        public List<_APKContents> FileContents
        {
            get { return fileContents; }
        }

        public _APKFile(String fileName)
        {
            FileStream cFile = new FileStream(fileName, FileMode.Open);
            GZipInputStream uFile = new GZipInputStream(cFile);
            _APKReader aReader = new _APKReader(uFile);
            List<Byte[]> fields = new List<Byte[]>();

            foreach(Byte[] field in aReader.ReadFields())
                fields.Add(field);

            //fields[0] = Length of Route Name
            //fields[1] = Route Name
            routeName = Encoding.Unicode.GetString(fields[1]);

            //fields[2] = Length of Folder Name
            //fields[3] = Folder Name
            folderName = Encoding.Unicode.GetString(fields[3]);

            //fields[4] = Length of Folder Name 2 
            //fields[5] = Folder Name 2
            //fields[6] = ???
            //fields[7] = ???
            //fields[8] = ???

            for (int index = 9; index <= fields.Count-2; index += 3)
            {
                //fields[9,12,...] = Length of File Name
                //fields[10,13,...] = File Name
                //fields[11,14,...] = File
                fileContents.Add(new _APKContents(fields[index + 1], fields[index + 2], (index >= fields.Count-3)));
            }
        }

        public bool ExportToZipFile(string fileName)
        {
            try
            {
                FileStream fsOut = File.Create(fileName);
                ZipOutputStream zipStream = new ZipOutputStream(fsOut);

                zipStream.SetLevel(9);

                foreach (_APKContents contents in fileContents)
                {
                    string entryName = contents.FileName;
                    entryName = ZipEntry.CleanName(entryName);

                    ZipEntry newEntry = new ZipEntry(entryName);
                    newEntry.Size = contents.Contents.Length;

                    zipStream.PutNextEntry(newEntry);
                    zipStream.Write(contents.Contents.ToArray(), 0, (int)contents.Contents.Length);
                }

                zipStream.Finish();
                zipStream.Close();

                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
                return false;
            }
        }

        public bool ExportToDirectory(string directory)
        {
            try
            {
                foreach (_APKContents contents in fileContents)
                {
                    string filePath = Path.Combine(directory, contents.FileName);
                    bool export = true;

                    if (File.Exists(filePath))
                    {
                        string fileName = Path.GetFileName(filePath);
                        string message = String.Format("The file: {0} already exists in the specified folder. Do you wish to overwrite?", fileName);
                        DialogResult result = MessageBox.Show(message, "File Already Exists", 
                            MessageBoxButtons.YesNo, MessageBoxIcon.Question);
                        export = (result == DialogResult.Yes); 
                    }

                    if (export)
                    {
                        string subDirectory = Path.GetDirectoryName(filePath);
                        if (!Directory.Exists(subDirectory))
                            Directory.CreateDirectory(subDirectory);

                        using (FileStream fsOut = File.Create(filePath))
                        {
                            fsOut.Write(contents.Contents.ToArray(), 0, (int)contents.Contents.Length);
                            fsOut.Close();
                        }
                    }
                }
                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                return false;
            }
        }
    }

    class _APKContents
    {
        private string fileName;
        private MemoryStream contents;

        public _APKContents(byte[] nameBuffer, byte[] contentsBuffer, bool lastFile)
        {
            fileName = Encoding.Unicode.GetString(nameBuffer);

            int bufferLength = lastFile ? contentsBuffer.Length : contentsBuffer.Length - 2;

            contents = new MemoryStream(contentsBuffer, 0, bufferLength);
        }

        public string FileName
        {
            get { return fileName; }
        }

        public MemoryStream Contents
        {
            get { return contents; }
        }
    }
 
    class _APKReader : BinaryReader
    {
        public _APKReader(Stream input)
            : base(input)
        { }

        public IEnumerable<byte[]> ReadFields()
        {
            List<Byte> bytes = new List<Byte>();
            Byte[] b = new Byte[2];
            long bytesRead = 2;
            while (bytesRead == 2)
            {
                bytesRead = this.Read(b, 0, 2);

                if ((bytesRead < 2) || (b[0] == 0 && b[1] == 0))
                {
                    yield return bytes.ToArray();
                    bytes.Clear();
                    continue;
                }
                else
                    bytes.AddRange(b);
            }
        }
    }
}
